<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>直播效果音播放器</title>
  <style>
    body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,Noto Sans;line-height:1.5;margin:16px}
    #toolbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:12px}
    .item{display:flex;align-items:center;gap:8px;margin:6px 0}
    button{padding:6px 10px;cursor:pointer}
    .key{color:#666;font-size:12px}
    #binding{color:#d43;font-weight:bold;display:none}
    .nameBtn{min-width:160px}
  </style>
</head>
<body>
  <div id="toolbar">
    <button id="pickDirBtn">选择文件夹</button>
    <button id="refreshBtn">刷新列表</button>
    <button id="clearBindBtn">清空绑定</button>
    <button id="stopBtn">停止音频</button>
    <button id="bindStopKeyBtn">绑定停止键</button>
    <span id="stopKeySpan" class="key">未设置停止键</span>
    <span id="binding"></span>
  </div>
  <div id="status">请先选择包含音频文件的文件夹。</div>
  <div id="list"></div>
  <small>提示：
    1) 选择文件夹后自动加载其中的音频文件；
    2) 切换播放时会自动停止上一条音频，避免同时播放；
    3) 点击“绑定键”后按任意键为该音频绑定；
    4) 可点击“绑定停止键”设置一个键盘按键，一按即可停止所有正在播放的音频；
    5) 绑定关系保存在 localStorage，仅保存「文件名-键位」和停止键；
    6) 若你在文件夹里增删音频，点击“刷新列表”即可同步。
  </small>
<script>
(() => {
  const storeKey = 'sfx-keymap-v1';
  const stopStoreKey = 'sfx-stop-key-v1';
  let keyMap = {}; // { filename: key }
  let stopKey = '';
  let items = [];  // { name, handle }
  let waitingBindName = null;
  let waitingBindStop = false;
  let dirHandle = null;
  const playing = new Set(); // 当前正在播放的 Audio 元素集合

  const pickDirBtn = document.getElementById('pickDirBtn');
  const refreshBtn = document.getElementById('refreshBtn');
  const clearBindBtn = document.getElementById('clearBindBtn');
  const stopBtn = document.getElementById('stopBtn');
  const bindStopKeyBtn = document.getElementById('bindStopKeyBtn');
  const stopKeySpan = document.getElementById('stopKeySpan');
  const listEl = document.getElementById('list');
  const statusEl = document.getElementById('status');
  const bindingEl = document.getElementById('binding');

  function loadKeyMap() {
    try { keyMap = JSON.parse(localStorage.getItem(storeKey) || '{}'); }
    catch { keyMap = {}; }
    try { stopKey = localStorage.getItem(stopStoreKey) || ''; } catch { stopKey = ''; }
    updateStopKeyView();
  }
  function saveKeyMap() { localStorage.setItem(storeKey, JSON.stringify(keyMap)); }
  function saveStopKey() { localStorage.setItem(stopStoreKey, stopKey || ''); }
  function updateStopKeyView(){ stopKeySpan.textContent = stopKey ? `停止键：${stopKey}` : '未设置停止键'; }

  async function pickDirectory() {
    try {
      dirHandle = await window.showDirectoryPicker();
      await loadFromDir();
    } catch (e) {
      console.warn('未选择文件夹或不支持：', e);
    }
  }

  async function loadFromDir() {
    if (!dirHandle) { statusEl.textContent = '请先选择包含音频文件的文件夹。'; return; }
    statusEl.textContent = '正在加载...';
    items = [];
    const exts = /\.(mp3|wav|ogg|m4a|flac)$/i;
    for await (const [name, handle] of dirHandle.entries()) {
      if (handle.kind === 'file' && exts.test(name)) {
        items.push({ name, handle });
      }
    }
    if (items.length === 0) statusEl.textContent = '该文件夹未找到音频文件。';
    else statusEl.textContent = `已加载 ${items.length} 个音频文件。`;
    render();
  }

  function stopAll() {
    if (playing.size === 0) return;
    playing.forEach(a => {
      try { a.pause(); a.currentTime = 0; } catch {}
      try { if (a.__url) URL.revokeObjectURL(a.__url); } catch {}
    });
    playing.clear();
  }

  async function play(item) {
    try {
      // 切换播放前先停止所有，避免同时播放两个音频
      stopAll();
      const file = await item.handle.getFile();
      const url = URL.createObjectURL(file);
      const audio = new Audio(url);
      audio.__url = url; // 记录以便停止时释放
      audio.onended = () => {
        try { URL.revokeObjectURL(url); } catch {}
        playing.delete(audio);
      };
      playing.add(audio);
      audio.play();
    } catch (e) { console.warn('播放失败:', e); }
  }

  function bindKey(item) {
    waitingBindName = item.name;
    bindingEl.style.display = 'inline';
    bindingEl.textContent = `正在为「${item.name}」绑定键位…请按键`;
  }
  function bindStopKey() {
    waitingBindStop = true;
    bindingEl.style.display = 'inline';
    bindingEl.textContent = '正在绑定停止键…请按键';
  }
  function unbindUI() {
    waitingBindName = null;
    waitingBindStop = false;
    bindingEl.style.display = 'none';
    bindingEl.textContent = '';
  }
  function removeBinding(name) {
    delete keyMap[name];
    saveKeyMap();
    render();
  }

  function render() {
    listEl.innerHTML = '';
    items.forEach(item => {
      const row = document.createElement('div');
      row.className = 'item';

      const playBtn = document.createElement('button');
      playBtn.className = 'nameBtn';
      playBtn.textContent = item.name.replace(/\.[^/.]+$/, '');
      playBtn.onclick = () => play(item);

      const keySpan = document.createElement('span');
      keySpan.className = 'key';
      const k = keyMap[item.name] || '';
      keySpan.textContent = k ? `键：${k}` : '未绑定';

      const bindBtn = document.createElement('button');
      bindBtn.textContent = '绑定键';
      bindBtn.onclick = () => bindKey(item);

      const delBindBtn = document.createElement('button');
      delBindBtn.textContent = '移除绑定';
      delBindBtn.onclick = () => removeBinding(item.name);

      row.appendChild(playBtn);
      row.appendChild(keySpan);
      row.appendChild(bindBtn);
      row.appendChild(delBindBtn);
      listEl.appendChild(row);
    });
  }

  window.addEventListener('keydown', (e) => {
    if (waitingBindStop) {
      stopKey = e.key;
      saveStopKey();
      updateStopKeyView();
      unbindUI();
      e.preventDefault();
      return;
    }
    if (waitingBindName) {
      keyMap[waitingBindName] = e.key;
      saveKeyMap();
      render();
      unbindUI();
      e.preventDefault();
      return;
    }
    if (stopKey && e.key === stopKey) { // 按下停止键
      stopAll();
      e.preventDefault();
      return;
    }
    const target = items.find(it => keyMap[it.name] && keyMap[it.name] === e.key);
    if (target) play(target);
  });

  pickDirBtn.addEventListener('click', pickDirectory);
  refreshBtn.addEventListener('click', loadFromDir);
  clearBindBtn.addEventListener('click', () => {
    if (Object.keys(keyMap).length === 0) return;
    if (confirm('确认清空所有键位绑定？')) {
      keyMap = {};
      saveKeyMap();
      render();
    }
  });
  stopBtn.addEventListener('click', stopAll);
  bindStopKeyBtn.addEventListener('click', bindStopKey);

  loadKeyMap();
})();
</script>
</body>
</html>